package spring.proxy;

import spring.aop.Advice;
import spring.aop.AdviceMethod;
import spring.aop.AopConfig;
import spring.aop.AopPointcut;
import spring.context.ApplicationContextHolder;
import spring.factory.DefaultListableBeanFactory;
import spring.transaction.Transaction;
import spring.transaction.TransactionBeanFactory;
import spring.transaction.TransactionManager;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.Map;
import java.util.Objects;

public class BeanHandler implements BeanInvocationHandler {

    private final Object originObject;
    private final TransactionBeanFactory beanFactory;
    private AopConfig aopConfig;

    public BeanHandler(Object originObject) {
        this.originObject = originObject;
        beanFactory = ApplicationContextHolder.getBeanFactory();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (beanFactory.isAnnotationBeanFactory()) {
            return executeAnnotation(method, args);
        }
        return executeNormal(method, args);
    }

    private Object executeAnnotation(Method method, Object[] args)
            throws Exception {
        DefaultListableBeanFactory factory = (DefaultListableBeanFactory) beanFactory;
        String classSign = originObject.getClass().toGenericString();
        String methodSign = originObject.getClass().getMethod(method.getName(), method.getParameterTypes()).toGenericString();
        if (!factory.beginTransactionIfNecessary(classSign) &&
                !factory.beginTransactionIfNecessary(methodSign)) {
            return method.invoke(originObject, args);
        }
        TransactionManager transactionManager = factory.getBean("transactionManager");
        Transaction transaction = transactionManager.getTransaction();
        try {
            // 执行方法
            Object result = method.invoke(originObject, args);
            transaction.commit();
            return result;
        } catch (Exception e) {
            transaction.rollback();
            throw e;
        } finally {
            transaction.close();
        }
    }

    private Object executeNormal(Method method, Object[] args)
            throws IllegalAccessException, InvocationTargetException, SQLException {
        if (Objects.isNull(aopConfig)) {
            aopConfig = beanFactory.getAopConfig();
        }
        String builder = getMethodName(method);
        Map.Entry<Advice, AopPointcut> advisor = aopConfig.getAdvisor(builder);
        if (Objects.isNull(advisor)) {
            return method.invoke(originObject, args);
        }
        Advice advice = advisor.getKey();
        AdviceMethod adviceMethod = advice.getAdviceMethod(method.getName());
        if (Objects.isNull(adviceMethod)) {
            return method.invoke(originObject, args);
        }
        Transaction transaction = advice.getTransaction();
        transaction.setReadOnly(adviceMethod.isReadOnly());
        try {
            // 执行方法
            Object result = method.invoke(originObject, args);
            transaction.commit();
            return result;
        } catch (Exception e) {
            transaction.rollback();
            throw e;
        } finally {
            transaction.close();
        }
    }

    private String getMethodName(Method method) {
        Class<?>[] types = method.getParameterTypes();
        StringBuilder builder = new StringBuilder(originObject.getClass().toGenericString() + "." + method.getName() + "(");
        for (int i = 0; i < types.length; i++) {
            builder.append(types[i].getName());
            if (i < (types.length - 1)) {
                builder.append(",");
            }
        }
        builder.append(")");
        return builder.toString();
    }

}
